namespace CookieTerm
{
    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;

	public class TelnetTerminal : Terminal
	{
        int defaultBufferSize = 4096;

        byte[] receiveBuffer;
        TelnetProtocolHandler handler;

		override public int TerminalWidth
		{
            get
            {
                return this.handler.windowSize.Width;
            }
			set 
			{
                ThrowIfConnected();
				this.handler.windowSize.Width = value;
			}
		}

        override public int TerminalHeight 
		{
            get
            {
                return this.handler.windowSize.Height;
            }
			set 
			{
                ThrowIfConnected();
                this.handler.windowSize.Height = value;
			}
		}

		override public string TerminalType
		{
            get
            {
                return this.handler.terminalType;
            }
			set 
			{
                ThrowIfConnected();
                this.handler.terminalType = value;
			}
		}

        protected byte[] ReceiveBuffer
        {
            get
            {
                if (this.receiveBuffer == null)
                {
                    this.receiveBuffer = new byte[this.defaultBufferSize];
                }
                return this.receiveBuffer;
            }
        }

        public TelnetTerminal()
        {
            this.handler = new TelnetProtocolHandler(
                new TelnetProtocolHandler.WriteCallback(this.Write),
                new TelnetProtocolHandler.SetLocalEchoCallback(this.SetLocalEcho),
                new TelnetProtocolHandler.NotifyEndOfRecordCallback(this.NotifyEndOfRecord));
        }

        protected override void AfterConnected()
        {
            this.handler.Reset();
        }

        public override void Send(byte[] data)
		{
			try 
			{
                this.handler.Transpose(data);
			} 
			catch (SocketException)
			{
				Disconnect();
			}
		}

		public override void Receive()
		{
            try
            {
                this.Socket.BeginReceive(ReceiveBuffer, 0, ReceiveBuffer.Length, 0, new AsyncCallback(ReceiveCallback), null);
            }
            catch (SocketException)
            {
                Disconnect();
            }
		}

		private void ReceiveCallback(IAsyncResult ar) 
		{
            if (!this.Socket.Connected)
            {
                return;
            }

            int bytesRead;
			try 
			{
				bytesRead = this.Socket.EndReceive(ar);
            }
            catch (SocketException)
            {
                Disconnect();
                return;
            }

			if (bytesRead > 0) 
			{
				this.handler.InputFeed(ReceiveBuffer, bytesRead);
				
                int payload = this.handler.Negotiate(ReceiveBuffer, bytesRead);
                if (payload > 0)
                {
                    // Notify the listeners that we have data.
                    byte[] data = new byte[payload];
                    Array.Copy(ReceiveBuffer, data, payload);
                    InvokeOnDataAvailable(new DataAvailableEventArgs(data));
                }

				// Get the rest of the data.
                Receive();
			} 
			else 
			{
				Disconnect();
			}
		}

        public void Write(byte[] b)
        {
            this.Socket.Send(b);
        }

		public void SetLocalEcho(bool echo) {}

        public void NotifyEndOfRecord() { }
	}
}
